Displaying 256 Colour PCX Pictures
Introduction
This article is intended to help newbie 80x86 coders to load and display a 256 colour PCX on the screen using VGA Mode 13h. Like in most other things these days file formats are explained in jargon filled tomes with a little C or pseudo-code if you are lucky, but sadly almost never any 80x86. This is quite sad when you consider that most coders learn 80x86 especially for graphics and it's sheer speed advantage.
The PCX file format is (c) by ZSoft Corporation and has been around for at least 11 years, so it's a well proven and easy to use format for simple graphics. It offers a quick, but not too impressive, form of compression to help reduce disk space using the RLE method of encoding spans of identical pixels using a "run" count.
It is probably worth download the offical ZSoft documentation before reading this article as it will help fill any gaps in my explanation.
The PCX File Format Header
Byte Item Size Description
---- ---- ---- -----------
0 Manufacturer 1 Byte ID flag byte = 10 decimal (0A hex)
1 Version 1 Byte Version information
0 = version 2.5
2 = version 2.8 with palette info.
3 = version 2.8 no palette
5 = version 3.0
2 Encoding 1 Byte 1 = .PCX run length encoding (RLE)
3 BitsPerPixel 1 byte Number of bits/pixel per plane
4 Xmin 1 word left co-ordinate of image
6 Ymin 1 word top co-ordinate of image
8 Xmax 1 word right co-ordinate of image
10 Ymax 1 word bottom co-ordinate of image
12 HRes 1 word Horizontal res. of creating device
14 VRes 1 word Vertical res. of creating device
16 Colormap 48 bytes EGA colour palette settings
64 -Reserved- 1 byte
65 NPlanes 1 byte Number of colour planes
66 BytesPerLine 1 word Number of bytes per scan line
68 PaletteInfo 1 word 1=color/bw, 2=greyscale palette
70 Filler 58 bytes padding bytes
The EGA/VGA 16 colour palette
The palette data for a 16 colour image is found at byte 16 in the 128 byte header. Although I will only give example code for 256 colour images, here is some info about 16 colour ones just in case you want to modify the code sometime in the future.
The 48 byte "Colormap" is made up from 16 triplets (3 byte items). Each byte can has a value from 0 to 255. For the EGA palette each one of the triplet bytes must be converted into one of 4 possible levels.
Triplet byte EGA Level
------------- -----------
0 to 63 decimal 0
64 to 127 1
128 to 192 2
193 to 255 3
The VGA 256 colour palette
Because there are now 256 possible colours there are 256 triplets, so in total there are 768 bytes for the VGA palette information.
The 768 bytes of palette data is stored as the last 768 bytes in the file and is preceeded by the byte 12 decimal (hex 0C).
To determine if a 768 byte (256 colour) palette is present in the picture file first check that the "Version" byte in the header at byte 1 = 05 and then seek back from the EOF (end-of-file) 768 bytes and read these into memory.
Each byte in this 768 block has a value of 00 to FF hex (255 decimal). Because the VGA palette registers can only use a value of 00 to 3F hex (63 decimal), each byte must be divided by 4 before being sent to the VGA palette registers.
The RLE (Run Length Encoding)
This is a way to reduce long runs of an identical byte into a 2 byte pair with the first byte being a repeat (or "run") count and the second being the actual byte/pixel to repeat.
The encoding is primative by LZ compression standards, but it can still save a reasonable amount of bytes from the file size. This simple encoding scheme can be written in about 26 bytes of code.
N Action
------------ --------
00 to BF hex Output a byte with the value N (00 to BF)
C0 to FF hex Use N as a "run" count and repeat the
next pixel/byte N-C0 hex times
So if we read a byte N and it has a value below C0 hex (192 decimal) we treat it as a raw, literal byte/pixel and output it.
If the byte N is above or equal to C0 hex (192 decimal) then we subtract C0 hex from it to get a 6 bit count with a value of 00 to 3F hex (0 to 63) then following byte is then repeatedly written this number of times.
[DS:SI] --> Address of a PCX compressed picture
(after the 128 byte header).
[ES:DI] --> Address of output buffer (E.g. A000:0000 hex)
DX = total length of uncompressed picture
DepackRLE PROC
CLD ; DF=1 make sure STOSB and LODSB
; always move up in memory.
@@next:
MOV CX, 0001h ; assume a "run" of 1 byte
LODSB ; AL = read the next byte()
CMP AL, 0C0h
JB @@raw1 ; is AL below C0 hex?
SUB AL,0C0h
MOV CL, AL ; otherwise CL = AL - C0 hex
LODSB ; AL = read the next byte()
@@raw1:
SUB DX, CX ; adjust total DX using CX count
JC @@corrupt ; has DX overflowed ?
REP STOSB ; repeat AL at [ES:DI], CX times
JNZ @@next ; continue while DX <> 0
@@corrupt:
RET
DepackRLE ENDP
The above code requires that the total size of the uncompressed picture be given in the DX register. This is normally 64000 for a 320x200 pixel image. This would decompress the entire image in one go.
You could of course decompress on an image on a line-by-line basis by calling "DepackRLE" 200 times with DX = 320 (where 320 = number of pixels per line).
Any single byte/pixel with a value between C0 and FF hex is prefixed by the byte C1 hex, this is a run of 1 byte.
Compressing an image using RLE (Run Length Encoding)
The following code can be used to compress the image data of a picture into the RLE format.
[DS:SI] --> Address image/block to compress
CX = Length of block to compress
[ES:DI] --> Address of the output buffer
PackRLE PROC
PUSH DI ; keep start address
@@pack:
MOV BL, 0C1h ; Count = 1 + C0 hex
LODSB ; get pixel/byte
DEC CX
JZ @@endrun ; final pixel/byte?
@@count:
CMP AL, [SI] ; does it match AL?
JNZ @@endrun ; end of run?
CMP BL, 0FFh
JZ @@endrun ; does BL = maxium count?
INC BL
INC SI
LOOP @@count ; otherwise continue match
@@endrun:
CMP AL, 0C0h
JAE @@prefix ; does pixel/byte need C1h prefix?
CMP BL, 0C1h
JZ @@rawbyte ; BL = count of 1?
@@prefix:
XCHG AX, BX
STOSB ; Output byte(BL) "run" count + C0 hex
XCHG AX, BX
@@rawbyte:
STOSB ; Output byte(AL)
TEST CX, CX
JNZ @@pack ; continue while CX<>0
MOV CX, DI
POP AX ; [DS:AX] --> RLE block address
SUB CX, AX ; CX = length of RLE block
RET
PackRLE ENDP
Loading the PCX file.
One of the quickest way to display a .PCX picture file is to read the entire file into memory and then decode it directly to the screen (or other buffer).
This seems little an easy task, after all a 320x200 pixel images only takes up 64000 bytes (hex FA00) and so fits easily into a 64Kb segment. Which is important if you are still using Real-Mode.
But, there is a problem.
For certain (highly detailed) picture images it is possible that the RLE compression will end up with a .PCX file which is bigger than 64Kb, even though the actual uncompressed RAW image is only 62.5 Kb (64000 bytes).
If we stick with loading the entire file, then decompress method we might need a 129 Kb buffer to handle the worst possible case of PCX file size. Or we could read a single byte at a time from the PCX file and decompress it that way. This would only require a 1 byte buffer, but of course would be very slow.
Perhaps the best solution is to use a 1 or 2 Kb cache into which you read 1 or 2Kb at a time then decompressing from this as needed. I have given some TASM source code which shows how this can be done. This method can be easily used to handle other large files in a similar way.
Dimensions of a PCX picture.
Of course you don't always want to use a 320x200 pixel image, you many want a smaller or larger size. In this case you need to discover the size of the PCX image from the 128-byte header.
You can find the size of the image like this:
[DS:SI] --> Address of PCX header
MOV BX, [SI+8] ; Xmax (right co-ordinate)
SUB BX, [SI+4] ; - Xmin (left co-ordinate)
INC BX ; plus 1
MOV CX, [SI+10] ; Ymax (bottom co-ordinate)
SUB CX, [SI+6] ; - Ymin (top co-ordinate)
INC CX ; plus 1
MOV AX, BX
MUL CX ; DX.AX = total pixels/bytes
This gives the dimensions of the PCX image, BX=width and CX=height, it also gives the total number of bytes/pixels in DX.AX which could be used to allocate memory.
Closing Words
There are far, far better file formats around such as the .GIF format which uses the LZW compression algorithm and so makes RLE look like the sad, overweight method it really is.
But because the PCX format is so simple with a fairly quick decompression stage and a vast number of paint programs supporting it, it can still be very useful, especially if you are new to 80x86 or programming in general.
You should be able to find the source for the previously mentioned PCX loaders somewhere with this article, probably in a ZIP file. It was written for TASM and so may require a little bit of "adjusting" so it will work with MASM too. Don't worry, I did NOT use IDEAL mode.
The source code should be fairly easy to understand and use. It is ONLY designed for 256 colour images and for 320x200 pixel images!! So please remember this when designing your graphics. Although this is quite limited as should be enough for newbie coders....
Enjoy.
Regards